package jsonlog

import (
	"encoding/json"
	"os"
	"path"
	"time"

	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"go.uber.org/zap/zapcore"
)

type Logger struct {
	config Config
	fields map[string]any
	writer zapcore.WriteSyncer
}

type Config struct {
	LogFileDir       string `yaml:"log_file_dir" json:"log_file_dir"`
	LogFileName      string `yaml:"log_file_name" json:"log_file_name"`
	RotateSize       int    `yaml:"rotate_size" json:"rotate_size"`
	RotateMaxBackups uint   `yaml:"rotate_max_backups" json:"rotate_max_backups"`
	RotateMaxAge     uint   `yaml:"rotate_max_age" json:"rotate_max_age"`
	RotateDay        int    `yaml:"rotate_day" json:"rotate_day"`
	WithoutTimeField bool   `yaml:"without_time_field" json:"without_time_field"` // 为True时不自动添加时间字段
	TimeKey          string `yaml:"time_key" json:"time_key" default:"time_iso8601"`
	TimeFormat       string `yaml:"time_format" json:"time_format" default:"2006-01-02T15:04:05Z"`
}

func NewLogger(cfg Config) *Logger {
	day := time.Hour * 24
	var w zapcore.WriteSyncer
	if cfg.LogFileName == "" {
		w = zapcore.AddSync(os.Stdout)
	} else {
		opts := []rotatelogs.Option{}

		// 默认走time滚动，除非指定size。
		if cfg.RotateDay > 0 {
			opts = append(opts, rotatelogs.WithRotationTime(day*time.Duration(cfg.RotateDay)))
		} else if cfg.RotateSize > 0 {
			// 按大小分割，如果时间一样，在时间后缀后面会自动加上一个数字后缀，以此区分同一时间的不同日志文件
			// 如果时间不一样，则生成新的时间后缀文件
			opts = append(opts, rotatelogs.WithRotationSize(int64(cfg.RotateSize)*1024*1024))
		}

		if cfg.RotateMaxAge > 0 {
			opts = append(opts, rotatelogs.WithMaxAge(day*time.Duration(cfg.RotateMaxAge)))
		} else if cfg.RotateMaxBackups > 0 {
			opts = append(opts, rotatelogs.WithRotationCount(uint(cfg.RotateMaxBackups)))
		}

		filePath := path.Join(cfg.LogFileDir, cfg.LogFileName+".%Y%m%d")
		r, err := rotatelogs.New(filePath, opts...)
		if err != nil {
			panic(err)
		}
		w = zapcore.AddSync(r)
	}

	if cfg.TimeKey == "" {
		cfg.TimeKey = "time_iso8601"
	}
	if cfg.TimeFormat == "" {
		cfg.TimeFormat = "2006-01-02T15:04:05Z"
	}

	return &Logger{
		config: cfg,
		fields: make(map[string]any),
		writer: w,
	}
}

func (l *Logger) WithField(key string, val any) *Logger {
	newFields := make(map[string]any)
	for k, v := range l.fields {
		newFields[k] = v
	}
	newFields[key] = val

	return &Logger{
		config: l.config,
		fields: newFields,
		writer: l.writer,
	}
}

func (l *Logger) preprocess(v interface{}) (data []byte, err error) {
	data, err = json.Marshal(v)
	if err != nil {
		return
	}

	if len(l.fields) == 0 && l.config.WithoutTimeField {
		return
	}

	doc := make(map[string]json.RawMessage)
	if err = json.Unmarshal(data, &doc); err != nil {
		return
	}

	// add extra fields
	for k, v := range l.fields {
		if _, ok := doc[k]; ok {
			continue
		}

		val, err := json.Marshal(v)
		if err == nil {
			doc[k] = json.RawMessage(val)
		}
	}

	// add time field
	if _, ok := doc[l.config.TimeKey]; !ok {
		timestr := time.Now().UTC().Format(l.config.TimeFormat)
		doc[l.config.TimeKey] = json.RawMessage([]byte(`"` + timestr + `"`))
	}

	return json.Marshal(doc)
}

func (l *Logger) Log(v interface{}) {
	data, err := l.preprocess(v)
	if err != nil || len(data) == 0 {
		return
	}

	data = append(data, '\n')
	_, _ = l.writer.Write(data)
}
